// Copyright (c) 2019-2025 Provable Inc.
// This file is part of the snarkOS library.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use snarkos_cli::{commands::CLI, helpers::Updater};
use snarkvm::utilities::display_error;

use clap::Parser;
#[cfg(feature = "locktick")]
use locktick::lock_snapshots;
#[cfg(feature = "locktick")]
use std::time::Instant;
use std::{backtrace::Backtrace, env, panic::catch_unwind};
use tracing::log::logger;

#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
use tikv_jemallocator::Jemalloc;

#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;

// Obtain information on the build.
include!(concat!(env!("OUT_DIR"), "/built.rs"));

/// Prints a message using `tracing::error` if logging is enabled, otherwise uses `eprintln`.
macro_rules! print_error {
    ($($arg:tt)*) => {
        if tracing::log::log_enabled!(tracing::log::Level::Error) {
            tracing::error!($($arg)*);
        } else {
            eprintln!($($arg)*);
        }
    };
}

/// Stops the process with the given exit code.
fn exit(exitcode: i32) -> ! {
    tracing::debug!("Stopping process with exitcode {exitcode}");

    // Ensure all log messages are written before the process terminates.
    logger().flush();

    // Perform the system call to exit the process.
    std::process::exit(exitcode);
}

fn main() {
    // A hack to avoid having to go through clap to display advanced version information.
    check_for_version();

    #[cfg(feature = "locktick")]
    std::thread::spawn(|| {
        loop {
            tracing::info!("[locktick] checking for active lock guards");
            let ts = Instant::now();
            let mut infos = lock_snapshots();
            infos.sort_unstable_by(|l1, l2| l1.location.cmp(&l2.location));

            for lock in infos {
                let mut guards = lock.known_guards.values().collect::<Vec<_>>();
                guards.sort_unstable_by(|g1, g2| g1.location.cmp(&g2.location));

                for guard in guards.iter().filter(|g| g.num_active_uses() != 0) {
                    let location = &guard.location;
                    let kind = guard.kind;
                    let num_uses = guard.num_uses;
                    let active_users = guard.num_active_uses();
                    let avg_duration = guard.avg_duration();
                    let avg_wait_time = guard.avg_wait_time();
                    tracing::info!(
                        "[locktick] {location} ({:?}): {num_uses}; {active_users} active; avg d: {:?}; avg w: {:?}",
                        kind,
                        avg_duration,
                        avg_wait_time
                    );
                }
            }
            tracing::debug!("[locktick] finished the check in {:?}", ts.elapsed());
            std::thread::sleep(std::time::Duration::from_secs(3));
        }
    });

    // Set a custom hook here to show "pretty" errors when panicking.
    std::panic::set_hook(Box::new(|err| {
        print_error!("⚠️ {}\n", err.to_string().replace("panicked at", "snarkOS encountered an unexpected error at"));

        // Always show backtraces.
        let backtrace = Backtrace::force_capture().to_string();

        let mut msg = "Backtrace:\n".to_string();
        msg.push_str("      [...]\n");

        // Remove all the low level frames.
        // This can be done more cleanly once the `backtrace_frames` feature is stabilized.
        let lines = backtrace.lines().skip_while(|line| !line.contains("core::panicking"));

        for line in lines {
            // Stop printing once we hit the panic handler.
            if line.contains("snarkos::main") {
                break;
            }

            msg.push_str(&format!("{line}\n"));
        }

        // Print the entire backtrace as a single log message.
        print_error!("{msg}");
    }));

    // Run the CLI.
    // We use `catch_unwind` here to ensure a panic stops execution and not just a single thread.
    // Note: `catch_unwind` can be nested without problems.
    let result = catch_unwind(|| {
        // Parse the given arguments.
        let cli = CLI::parse();

        // Run the updater.
        if !cli.noupdater
            && let Some(msg) = Updater::print_cli()
        {
            println!("{msg}");
        }

        // Run the CLI.
        cli.command.parse()
    });

    // Process any errors (including panics).
    match result {
        Ok(Ok(output)) => {
            println!("{output}\n");
            exit(0);
        }
        Ok(Err(err)) => {
            // A regular error occurred.
            display_error(&err);
            eprintln!();
            eprintln!("Use `--help` for instructions on how to use this command");

            exit(1);
        }
        Err(_) => {
            print_error!(
                "This is most likely a bug!\n\
                Please report it to the snarkOS developers: https://github.com/ProvableHQ/snarkOS/issues/new?template=bug.md"
            );

            exit(1);
        }
    }
}

/// Checks whether the version information was requested and - if so - display it and exit.
fn check_for_version() {
    if let Some(first_arg) = env::args().nth(1) {
        if ["--version", "-V"].contains(&&*first_arg) {
            let branch = GIT_HEAD_REF.unwrap_or("unknown_branch");
            let commit = GIT_COMMIT_HASH.unwrap_or("unknown_commit");
            let mut features = FEATURES_LOWERCASE_STR.to_owned();
            features.retain(|c| c != ' ');

            println!("snarkos {branch} {commit} features=[{features}]");

            exit(0);
        }
    }
}
